Rotatividade de clientes - Prevendo a probabilidade de churn de Clientes de uma operadora de telecom.

Victor Saraiva Rocha

15/08/2021


Projeto de conclusão de curso -> Data Science Academy


1. Descrição do Projeto


Customer Churn (ou Rotatividade de Clientes, em uma tradução livre) refere-se a uma decisão tomada pelo cliente sobre o término do relacionamento comercial. Refere-se também à perda de clientes. A fidelidade do cliente e a rotatividade de clientes sempre somam 100%.

Nesse projeto utilizaremos ferramentas da estatísticas para nos ajudar a retirar informações dos dados que auxiliem numa melhor tomada de decisão. Além disso, através de gráficos conseguiremos identificar facilmente onde é necessário maiores esforços para retenção dos clintes, ou seja, onde existe maior percentual de churn.

Através do algoritmo kmeans realizaremos segmentação dos clientes para identificarmos quais os clientes que mais fazem ligações e quais os clientes que mais gastam minutos por ligação.

Por fim, criaremos um algoritmo para prever se um cliente pode ou não cancelar seu plano e qual a probabilidade de isso ocorrer.

1.1 Objetivo

Será considerado um algoritmo válido caso possua uma acurácia de ao menos 80%.

2. Coletando os dados


In []:
# Importanto as bibliotecas básicas necessárias para o processo de Data Munging.

# Ignorando mensagens de avisos.
import warnings
warnings.filterwarnings("ignore")

# Importando o Numpy e Pandas
import numpy as np
import pandas as pd

# Importando pacotes para visualização.
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
import joypy as joy
import plotly.graph_objects as go
import plotly.offline as pyo
from plotly.subplots import make_subplots
pyo.init_notebook_mode()

# pacote de statística
from scipy.stats import binom
from scipy.stats import probplot
In []:
# Lendo os dados de treino e de teste.
treino = 'projeto4_telecom_treino.csv'
teste = 'projeto4_telecom_teste.csv'

dados_treino_bruto = pd.read_csv(treino)
dados_teste_bruto = pd.read_csv(teste)

# Verificando o shape dos dados.
print('Dados de treino: ', dados_treino_bruto.shape)
print('Dados de teste: ', dados_teste_bruto.shape)
Dados de treino:  (3333, 21)
Dados de teste:  (1667, 21)

Vejamos algumas informações sobre como as variáveis foram carregadas nos dados de treino.

In []:
# Informações sobre os dados de treino.
dados_treino_bruto.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3333 entries, 0 to 3332
Data columns (total 21 columns):
Unnamed: 0                       3333 non-null int64
state                            3333 non-null object
account_length                   3333 non-null int64
area_code                        3333 non-null object
international_plan               3333 non-null object
voice_mail_plan                  3333 non-null object
number_vmail_messages            3333 non-null int64
total_day_minutes                3333 non-null float64
total_day_calls                  3333 non-null int64
total_day_charge                 3333 non-null float64
total_eve_minutes                3333 non-null float64
total_eve_calls                  3333 non-null int64
total_eve_charge                 3333 non-null float64
total_night_minutes              3333 non-null float64
total_night_calls                3333 non-null int64
total_night_charge               3333 non-null float64
total_intl_minutes               3333 non-null float64
total_intl_calls                 3333 non-null int64
total_intl_charge                3333 non-null float64
number_customer_service_calls    3333 non-null int64
churn                            3333 non-null object
dtypes: float64(8), int64(8), object(5)
memory usage: 546.9+ KB

Aparentemente não temos nenhuma variável com valor nulo. Contudo, vamos garantir que realmente não temos valores nulos e nem NA.

In []:
# Verificando se há valores nulos ou NA's.
print('Valores NAs nos dados de treino: ', dados_treino_bruto.isna().sum().sum())
print('Valores nulos nos dados de treino: ', dados_treino_bruto.isnull().sum().sum())
print('Valores duplicados: ', dados_treino_bruto.duplicated().sum())
Valores NAs nos dados de treino:  0
Valores nulos nos dados de treino:  0
Valores duplicados:  0
In []:
# 5 primeiras linhas.
dados_treino_bruto.head()
Out[]:
Unnamed: 0 state account_length area_code international_plan voice_mail_plan number_vmail_messages total_day_minutes total_day_calls total_day_charge ... total_eve_calls total_eve_charge total_night_minutes total_night_calls total_night_charge total_intl_minutes total_intl_calls total_intl_charge number_customer_service_calls churn
0 1 KS 128 area_code_415 no yes 25 265.1 110 45.07 ... 99 16.78 244.7 91 11.01 10.0 3 2.70 1 no
1 2 OH 107 area_code_415 no yes 26 161.6 123 27.47 ... 103 16.62 254.4 103 11.45 13.7 3 3.70 1 no
2 3 NJ 137 area_code_415 no no 0 243.4 114 41.38 ... 110 10.30 162.6 104 7.32 12.2 5 3.29 0 no
3 4 OH 84 area_code_408 yes no 0 299.4 71 50.90 ... 88 5.26 196.9 89 8.86 6.6 7 1.78 2 no
4 5 OK 75 area_code_415 yes no 0 166.7 113 28.34 ... 122 12.61 186.9 121 8.41 10.1 3 2.73 3 no

5 rows × 21 columns

Podemos ver que temos uma coluna chamada Unnamed que aparentemente se trata do indice dos dados. Faremos sua remoção e em seguida passaremos à algumas análises exploratória nos dados.

3. Análise exploratória dos dados


In []:
# Removendo a coluna Unnamed
dados_treino_bruto = dados_treino_bruto.drop('Unnamed: 0', axis=1)

# Verificando quantidade de estados.
print('Quantidade total de estados: ', len(dados_treino_bruto['state'].unique()))
Quantidade total de estados:  51

Para facilitar a leitura dos estados faremos a conversão da sigla ao nome do estado utilizando o dataset disponibilizado pelo site: github - cphalpert.

In []:
# realizando a leitura do dataset.
states_name = pd.read_csv('states_name.csv')

# Imprimindo as 5 primeiras linhas dos dados.
states_name.head()
Out[]:
State State Code Region Division
0 Alaska AK West Pacific
1 Alabama AL South East South Central
2 Arkansas AR South West South Central
3 Arizona AZ West Mountain
4 California CA West Pacific
In []:
# Removendo a coluna Division
states_name = states_name.drop('Division', axis=1)

# Alterando o nome da coluna State code para state
states_name = states_name.rename(columns={'State Code': 'state'})

# Validando a transformação
states_name.head()
Out[]:
State state Region
0 Alaska AK West
1 Alabama AL South
2 Arkansas AR South
3 Arizona AZ West
4 California CA West
In []:
# Agregando o nome e região dos estados aos nossos dados.
dados_treino_bruto = dados_treino_bruto.merge(states_name, sort=True)

# removendo a coluna state antiga
dados_treino_bruto = dados_treino_bruto.drop('state', axis=1)

# Verificando as 5 primeiras linhas
dados_treino_bruto.head()
Out[]:
account_length area_code international_plan voice_mail_plan number_vmail_messages total_day_minutes total_day_calls total_day_charge total_eve_minutes total_eve_calls ... total_night_minutes total_night_calls total_night_charge total_intl_minutes total_intl_calls total_intl_charge number_customer_service_calls churn State Region
0 36 area_code_408 no yes 30 146.3 128 24.87 162.5 80 ... 129.3 109 5.82 14.5 6 3.92 0 no Alaska West
1 136 area_code_415 yes yes 33 203.9 106 34.66 187.6 99 ... 101.7 107 4.58 10.5 6 2.84 3 no Alaska West
2 104 area_code_408 no no 0 278.4 106 47.33 81.0 113 ... 163.2 137 7.34 9.8 5 2.65 1 no Alaska West
3 127 area_code_510 no yes 36 183.2 117 31.14 126.8 76 ... 263.3 71 11.85 11.2 8 3.02 1 no Alaska West
4 126 area_code_415 no no 0 58.2 94 9.89 138.7 118 ... 136.8 91 6.16 11.9 1 3.21 5 yes Alaska West

5 rows × 21 columns

In []:
# Verificando se foram gerados valores nulos em nossa junção.
print('valores NA nas colunas:\n',dados_treino_bruto[['State', 'Region']].isna().sum())
print('\nvalores Nulos nas colunas:\n',dados_treino_bruto[['State', 'Region']].isnull().sum())

# Salvando os dados num arquivo csv.
dados_treino_bruto.to_csv('treino_state_region.csv')
valores NA nas colunas:
 State     0
Region    0
dtype: int64

valores Nulos nas colunas:
 State     0
Region    0
dtype: int64
In []:
dados_treino_bruto.columns = ['tempo_conta', 'codigo_area', 'srv_internacional', 'srv_caixa_postal',  'mensagens_voz',
                             'minutos_lig_manha', 'ligacoes_manha', 'taxa_lig_manha', 'minutos_lig_tarde', 'ligacoes_tarde',
                              'taxa_lig_tarde', 'minutos_lig_noite', 'ligacoes_noite', 'taxa_lig_noite', 'minutos_lig_internacional',
                              'ligacoes_internacional', 'taxa_lig_internacional', 'ligacoes_SAC', 'churn', 'estado', 'regiao']

dados_treino_bruto['minutos_total'] = dados_treino_bruto[[col for col in dados_treino_bruto.columns \
                                                           if 'minutos' in col]].sum(axis=1)

dados_treino_bruto['ligacoes_total'] = dados_treino_bruto[[col for col in dados_treino_bruto.columns \
                                                           if 'ligacoes' in col]].sum(axis=1)

dados_treino_bruto['taxa_total'] = dados_treino_bruto[[col for col in dados_treino_bruto.columns \
                                                           if 'taxa' in col]].sum(axis=1)

conditions = [
    (dados_treino_bruto['srv_internacional'] == 'yes') & (dados_treino_bruto['srv_caixa_postal'] == 'yes'), 
    (dados_treino_bruto['srv_internacional'] == 'yes') | (dados_treino_bruto['srv_caixa_postal'] == 'yes'),
    (dados_treino_bruto['srv_internacional'] == 'no') & (dados_treino_bruto['srv_caixa_postal'] == 'no')]

choices = [int(2), int(1), int(0)]

dados_treino_bruto['Qntd_servicos'] = np.select(conditions, choices, default=np.nan)

dados_treino_bruto['churn'] = [0 if x=='no' else 1 for x in dados_treino_bruto['churn']]
In []:
media_churn = dados_treino_bruto['churn'].mean()
dados_treino_bruto.groupby('churn').size() / len(dados_treino_bruto)
Out[]:
churn
0    0.855086
1    0.144914
dtype: float64

O percentual de churn é de ~15%. Utilizaremos a função massa de probabilidade binominal para nos ajudar a identificar qual a probabilidade de um número determinado de clientes dar churn.

In []:
# criando um range de clientes para verificar probabilidade de churn.
tamanho = range(10, 1000, 10)

# Aplicando a função massa de probabilidade binominal.
probs = binom.pmf(tamanho, len(dados_treino_bruto), media_churn)
fig, ax = plt.subplots(figsize=(15,10))
plt.stem(tamanho, probs)
plt.ylabel('Probabilidade', size=15)
plt.xlabel('Número de churns (clientes)', size=15)
Out[]:
Text(0.5, 0, 'Número de churns (clientes)')

É possível notar que a maior probabilidade de churn está em torno de 500 clientes. Vamos utilizar a função distribuição acumulada para verificarmos a probabilidade de que até 500 clientes realizem churn com uma média de 14,4914% encontrada anteriormente.

In []:
# Aplicando a função distribuição acumulada.
binom.cdf(500, 3333, media_churn)
Out[]:
0.805831699792162

Vemos que a probabilidade de que não mais 500 clientes deêm churn com uma média de 14,4914% é de 80%. Disso podemos dizer que as chances de que mais de 500 dêem churn é de 20%.

Criaremos uma classe para nos ajudar com as análises.

In []:
# classe para análises.
class DatAnalysis:
    
    def __init__(self):
        pass
    
    # Gráfico de hisotrama e QQPlot
    def probahist(self, dados, coluna):
        fig, ax = plt.subplots(figsize=(8,6))
        probplot(dados[coluna], plot=plt)
        plt.title('QQPlot - {}'.format(coluna), size=20)
        
        # Histograma
        fig = go.Figure()
        fig.add_trace(
            go.Histogram(x=dados[coluna], name='', marker_color='#342870', histnorm='probability'))
        
        fig.update_layout(title_text='Histograma <b>{}</b>'.format(coluna),
                              plot_bgcolor='white',
                              xaxis=dict(
                                  title=coluna), height=500, width=800)
        
        fig.show()
        
    
    # Percentual de clientes por variável.
    def Scatter_churn(self, data, coluna):
        if (coluna=='churn'):
            return print('Informar coluna numérica diferente de churn')
        if (data[coluna].dtypes=='int64' or data[coluna].dtypes=='float64'):
            grouped = data.groupby(coluna)['churn'].mean()
            fig = go.Figure()
            fig.add_trace(go.Scatter(
                name='',
                mode='markers',
                y=grouped.values,
                x=grouped.index,
                marker=dict(color='#45d96a', 
                            size=7,
                            line= dict(width=1))))
            
            fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0, 0, 0, 0.12)')
            fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0, 0, 0, 0.12)')


            fig.update_layout(title_text='Correlação de <b>{}</b> com a taxa de <b>churn</b>'.format(coluna),
                              plot_bgcolor='white',
                              xaxis=dict(
                                  title=coluna),
                              yaxis=dict(
                                  title='Taxa de Churn'))
            
            # Criando uma variável para plotagem da correlação entre as variáveis.
            correlacao = data.groupby(coluna)[['churn']].mean().reset_index().corr()['churn'][0]
            fig.add_annotation(text=f"<b>Correlação: {round(correlacao, 2)}</b>",
                  xref="paper", yref="paper", font=dict(color="#008a0e"),
                  x=0, y=1.1, showarrow=False)

            fig.show()
            
        else:
            print(f'{coluna} Não é uma variável numérica')

    # Função para comparar percentual de churn por agrupamento (percentual de todos os valores).
    def PercentChurn(self, data, coluna):
        dados = data.copy()
        dados[coluna] = dados[coluna].astype('category')
        grouped = dados.groupby(coluna)['churn'].mean().sort_values(ascending=False)
        fig = go.Figure()
        fig.add_trace(go.Bar(
            name='',
            y=grouped.index,
            x=grouped.values*100,
            orientation='h',
            marker_color='#9fa2c7', hovertemplate='%{x:.2f}%'))
        if len(grouped.index) > 10:
            ht=1000
        else:
            ht=400
        fig.update_layout(title_text='Percentual de Churn - <b>{}</b>'.format(coluna),
                          height=ht, plot_bgcolor='white',
                          xaxis=dict(
                              title='Taxa de churn',
                              titlefont_size=16),
                          yaxis=dict(
                              tickmode = 'linear'))
        fig.show()
        
    # Função para avaliar a média de variáveis por agrupamento.
    def clientes_box(self, data, grupo, coluna, lines=10):
        fig = make_subplots(rows=2, cols=2, row_heights=[0.33, 0.33], 
                           specs=[[{"type": "bar"}, {"type": "bar"}],
                                  [{"type": "box", "colspan": 2}, None]],
                           subplot_titles=("Percentual de clientes - <b>{}</b>".format(grupo),
                                           "Quantidade de clientes - <b>{}</b>".format(grupo),
                                          "Boxplot - {}  X {}".format(grupo, coluna)),
                           vertical_spacing = 0.10)
        
        # Gráfico de percentual de clientes (1x1)
        dados = data.copy()
        dados[grupo] = dados[grupo].astype('category')
        grouped = pd.DataFrame()
        grouped['percent'] = dados[grupo].value_counts() / dados[grupo].value_counts().sum() * 100
        grouped['clientes'] = dados[grupo].value_counts()
        grouped.sort_values(by='percent', ascending=False, inplace=True)
        grouped = grouped.head(lines)
        fig.add_trace(go.Bar(
            name='',
            y=grouped.index,
            x=grouped['percent'],
            orientation='h',
            marker_color='#7b8c26', hovertemplate='%{x:.2f}%'), row=1, col=1)
        
        # Gráfico de Quantidade de clientes (1x2)
        fig.add_trace(go.Bar(
            name='',
            y=grouped.index,
            x=grouped['clientes'],
            orientation='h',
            marker_color='#ebc334', hovertemplate='%{x} Clientes'), row=1, col=2)
        
        # Box plot (2x2)
        grouped3 = data[data[grupo].isin(grouped.index)]
        fig.add_trace(
            go.Box(y=grouped3[coluna], x=grouped3[grupo], name='',  boxpoints='outliers', boxmean='sd',
                   marker_color='#342870'),row=2, col=1)
        
        fig.update_xaxes(title_text=grupo, row=2, col=1)
        fig.update_yaxes(title_text=coluna, showgrid=True, gridcolor='#b5b5b5', gridwidth=0, row=2, col=1)
    
        
        if (lines>29):
            ht = 1800
            wd = 1400
        else:
            ht = 800
            wd =1000
            
        fig.update_layout(title=dict(text='<b>Dashboard Analítico - {} x {}</b>'.format(grupo, coluna),
                                    font_size=20),
                          height=ht, width=wd, plot_bgcolor='white',
                          xaxis=dict(
                              titlefont_size=16),
                          yaxis=dict(
                              tickmode = 'linear'), showlegend=False)
        
        fig.show()
        
        
        
# Criando uma instância da classe
analisar = DatAnalysis()

Vamos iniciar nossas análises verificando o percentual de churn para cada variável categórica de nossos dados.

In []:
# Criando uma lista apenas com as variáveis categóricas e numéricas
categoricas = [x for x in dados_treino_bruto.columns if dados_treino_bruto[x].dtypes=='object']
numericas = [x for x in dados_treino_bruto.columns if (dados_treino_bruto[x].dtypes=='int64' or \
                                                       dados_treino_bruto[x].dtypes=='float64')]

# Removendo a variável churn.
numericas.remove('churn')
In []:
# Verificando o percentual de churn por código de área.
analisar.PercentChurn(dados_treino_bruto, categoricas[0])

Não vemos diferença significativa na taxa de churn entre os códigos de área.

In []:
# Verificando o percentual de churn por plano international.
analisar.PercentChurn(dados_treino_bruto, categoricas[1])

Aqui é possível notar uma diferença bastante significativa. Clientes que possuem plano international apresentam maior tendência à churn. A diferença é de ~269%.

In []:
# Verificando o percentual de churn por serviço de caixa postal.
analisar.PercentChurn(dados_treino_bruto, categoricas[2])

Clientes que não possuem o serviço de caixa postal apresentam uma tendência à churn ~93% maior do que os clientes que possuem o plano.

In []:
# Verificando o percentual de churn por estado.
analisar.PercentChurn(dados_treino_bruto, categoricas[3])

Embora os estados de Califórnia e New Jersey apresentem as maiores taxas de churn, a partir de Arkansas todos os estados estão acima de 20% de churn.

Hawaii, Alaska, Arizona e Virgina são os estados que apresentam menor taxa de churn.

In []:
# Verificando o percentual de churn por região.
analisar.PercentChurn(dados_treino_bruto, categoricas[4])

Não é possível notar uma diferença significativa entre as regiões, entretanto, a região Northeast é a que apresenta maior taxa de churn.

In []:
# Verificando o percentual de churn por quantidade de ligaçoes ao SAC.
analisar.PercentChurn(dados_treino_bruto, numericas[14])

Podemos perceber que a partir de de 4 ligações ao SAC a chances de churn aumentam 346% em relação à 3 ligações. Necessário atenção às ligações ao SAC e tentar garantir que os clientes liguem até 3 vezes, no máximo, ao SAC. Também é interessante averiguar aos motivos dos clientes entrarem em contato com o SAC.

In []:
# Verificando o percentual de churn por quantidade de serviços.
analisar.PercentChurn(dados_treino_bruto, numericas[18])

Aqui fica fácil notar que clientes com 2 serviços (correio de voz e e plano internacional) apresentam uma taxa de churn ~186% maior que clientes com 1 ou nenhum serviço.

Não vemos diferença significativa entre clientes com 1 ou nenhum serviço.

Vejamos agora a correlação entre as variáveis numéricas e a taxa de churn.

In []:
# Correlação de churn com tempo de conta.
analisar.Scatter_churn(dados_treino_bruto, numericas[0])

Não vemos uma correlação entre o tempo da conta com a taxa de churn.

In []:
# Correlação de churn com número de mensagens.
analisar.Scatter_churn(dados_treino_bruto, numericas[1])

Aqui temos uma fraca correlação positiva, quando o número de mensagens de voz aumenta percebemos um leve aumento no percentual de churn.

In []:
# Verificando o percentual de churn por minutos totais na manhã.
analisar.Scatter_churn(dados_treino_bruto, numericas[2])

Aqui vemos uma fraca correlação positiva, onde quanto mais minutos em ligações pela manhã mais vemos um leve aumento no percentual de churn.

In []:
# Verificando o percentual de churn por total de ligaçoes na manhã.
analisar.Scatter_churn(dados_treino_bruto, numericas[3])

Não temos uma correlação entre o número de ligações com o churn.

In []:
# Verificando o percentual de churn por total de taxa na manhã.
analisar.Scatter_churn(dados_treino_bruto, numericas[4])

Aqui percebemos que temos a mesma correlação entre os minutos pela manhã e a taxa das ligações na manhã. Vejamos qual a correlação entre os minutos e as taxas em todos os períodos.

In []:
dados_treino_bruto[['minutos_lig_manha','taxa_lig_manha', 'minutos_lig_tarde', 'taxa_lig_tarde',
                   'minutos_lig_noite', 'taxa_lig_noite', 'minutos_lig_internacional', 'taxa_lig_internacional']].corr()
Out[]:
minutos_lig_manha taxa_lig_manha minutos_lig_tarde taxa_lig_tarde minutos_lig_noite taxa_lig_noite minutos_lig_internacional taxa_lig_internacional
minutos_lig_manha 1.000000 1.000000 0.007043 0.007029 0.004323 0.004300 -0.010155 -0.010092
taxa_lig_manha 1.000000 1.000000 0.007050 0.007036 0.004324 0.004301 -0.010157 -0.010094
minutos_lig_tarde 0.007043 0.007050 1.000000 1.000000 -0.012584 -0.012593 -0.011035 -0.011067
taxa_lig_tarde 0.007029 0.007036 1.000000 1.000000 -0.012592 -0.012601 -0.011043 -0.011074
minutos_lig_noite 0.004323 0.004324 -0.012584 -0.012592 1.000000 0.999999 -0.015207 -0.015180
taxa_lig_noite 0.004300 0.004301 -0.012593 -0.012601 0.999999 1.000000 -0.015214 -0.015186
minutos_lig_internacional -0.010155 -0.010157 -0.011035 -0.011043 -0.015207 -0.015214 1.000000 0.999993
taxa_lig_internacional -0.010092 -0.010094 -0.011067 -0.011074 -0.015180 -0.015186 0.999993 1.000000

Conforme podemos ver a correlação entre os minutos e a taxa é de 100%. Ou seja, certamente quanto mais minutos são utilizamos maior será a taxa paga.

Dessa forma verificaremos a correlação apenas entre as variáveis com minutos.

In []:
# Verificando o percentual de churn por total de minutos à tarde.
analisar.Scatter_churn(dados_treino_bruto, numericas[5])

As ligações à tarde apresentam uma correlação mais fraca com o churn do que as ligações pela manhã.

In []:
# Verificando o percentual de churn por total de ligaçoes à tarde.
analisar.Scatter_churn(dados_treino_bruto, numericas[6])

As ligaçoes à tarde apresentam uma fraca correlação positiva com o percentual de churn, diferente das ligações realizadas pela manhã.

Pularemos a correlação com a taxa total à tarde pois sabemos que apresenta a mesma correlação que os minutos.

In []:
# Verificando o percentual de churn por total de minutos à noite.
analisar.Scatter_churn(dados_treino_bruto, numericas[8])

Não vemos uma correlação entre o total de minutos à noite com o churn.

In []:
# Verificando o percentual de churn por total de ligações à noite.
analisar.Scatter_churn(dados_treino_bruto, numericas[9])

Temos uma correlação positiva, entretanto, muito fraca.

In []:
# Verificando o percentual de churn por total de minutos em ligaçoes internacionais.
analisar.Scatter_churn(dados_treino_bruto, numericas[11])

Podemos ver que temos uma fraca correlação positiva, onde o aumento de minutos internacionais aumento a chances de chun.

In []:
# Verificando o percentual de churn por total de ligaçõesinternacionais.
analisar.Scatter_churn(dados_treino_bruto, numericas[12])

Aqui também temos uma fraca correlação positiva entre as o total de ligaçoes internacionais com o churn. Mas apararemente se deve aos outliers.

In []:
# Verificando o percentual de churn por total de minutos em ligaçoes internacionais.
analisar.Scatter_churn(dados_treino_bruto, numericas[14])

Uau! Aqui vemos uma forte correlação positiva, o que significa que quanto mais ligaçoes o cliente tiver ao SAC maior será suas chances de churn.

In []:
# Verificando o percentual de churn por total de minutos em ligaçoes internacionais.
analisar.Scatter_churn(dados_treino_bruto, numericas[15])

Fraca correlação positiva entre os minutos totais em ligações e a taxa de churn.

In []:
# Verificando o percentual de churn por total de minutos em ligaçoes internacionais.
analisar.Scatter_churn(dados_treino_bruto, numericas[16])

Aqui percebemos uma correlação positiva muito leve entre o total de ligações e o churn.

In []:
# Verificando o percentual de churn por total de minutos em ligaçoes internacionais.
analisar.Scatter_churn(dados_treino_bruto, numericas[17])

Aqui percebemos que a taxa total é possui uma correlação um pouco superior ao total de minutos.

Vejamos mais algumas informaçoes de nosos dados.

In []:
# Análise de serviço internacional X tempo de conta.
analisar.clientes_box(dados_treino_bruto, categoricas[0], numericas[0])
In []:
# Histograma e QQPlot - Tempo de conta.
analisar.probahist(dados_treino_bruto, numericas[0])

Podemos enxergar alguns outliers em todos os códigos de áreas, ou seja, clientes que possuem um tempo de conta consideravelmente maior que os demais.

Aqui também podemos notar que o código de área 415 é o que possui a maior concentração de clientes 49,65% ou 1655 cilentes, embora todos os códigos de áreas apresentem a mesma taxa de churn. O código de área 510 possui o cliente com maior tempo de conta 243 meses.

Também podemos ver que em ambos os códigos de áreas temos praticamente uma mesma distribuição distribuição dos dados, com uma média de conta de ~101 meses e com desvio padrão de ~40 meses.

Através do histograma conseguimos ver quea a maioria dos clientes possuem o serviço em torno de 85 à 120 meses e que os dados estão muito próximos de uma distribuição normal, portanto, podemos utilizar o teorema do limite central. Através do QQPlot confirmamos que os dados estão quase numa normal - quanto mais em cima da reta os pontos estiverem mais os dados se aproximam de uma distribuição normal.

Vejamos as demais variáveis categóricas com o tempo de conta.

In []:
# Análise de serviço internacional com tempo de conta.
analisar.clientes_box(dados_treino_bruto, categoricas[1], numericas[0])

Podemos ver que 90,31% dos clientes não possuem o serviço internacional, isso é muito bom, pois vimos anteriormente que a taxa de churn de clientes com plano internacional é ~269% superior aos que não possuem.

Aqui vemos que os clientes que deram churn possuem a conta em média 4 meses antes do que os que não deram churn.

In []:
# Análise de serviço de caixa postal com tempo de conta.
analisar.clientes_box(dados_treino_bruto, categoricas[2], numericas[0])

Aqui não temos um cenário muito bom. Vimos anteriormente que clientes que não possuem um serviço de caixa postal tendem a dar ~93% mais churn do que os clientes que possuem. Seria interassante oferecer aos clientes o serviço de caixa postal e averiguar o motivo de ainda não o terem.

Os tempos de conta dos clientes estão bem distribuidos entre os que possuem caixa postal e não.

In []:
# Análise de estados com tempo de conta.
analisar.clientes_box(dados_treino_bruto, categoricas[3], numericas[0], 51)

Aqui podemos ver que a Califórnia é um estado onde a empresa possui poucos clientes e os clientes que possuem apresentam um alto percentual de churn comparado aos demais estados. Averiguar o motivo desses clientes não gostarem dos serviços.

Em todos os estados as distribuições de tempo de conta estão muito próximas de uma normal, entretanto, é possível enxergar que no estado de Illinois existem clientes com altos tempos de conta, visto que a média é puxada para cima.

In []:
# Análise de região X tempo de conta.
analisar.clientes_box(dados_treino_bruto, categoricas[4], numericas[0])

Podemos constatar que a região Nordeste além de ser a região com menor número de clientes é a região que maior apresenta churn.

Não faremos essa análise para todas as variáveis numéricas, apenas para as: minutos_total, ligacoes_total e taxa_total.

In []:
# Análise de código de área X minutos total.
analisar.clientes_box(dados_treino_bruto, categoricas[0], numericas[-4])
In []:
# Histograma e QQPlot - Minutos Total
analisar.probahist(dados_treino_bruto, 'minutos_total')

Aqui conseguimos notar que a média de minutos em todas as três áreas é muito similar ~590 minutos. Além disso, podemos notar outliers em todas as áreas, entretanto a área 415 possui uma quantidade maior de clientes com um total de minutos inferior aos demais.

Através do histograma e QQPlot, podemos ver que os minutos totais estão muito muito próximos de uma distribuição normal e que a maior concentração de minutos está entre 560 e 619 minutos.

In []:
# Análise de serviço internacional X minutos total.
analisar.clientes_box(dados_treino_bruto, categoricas[1], numericas[-4])

Aqui vemos que entre os clientes que possuem e não o serviço internacional não vemos uma diferença na quantidade de minutos utilizados.

In []:
# Análise de serviço de caixa postal X minutos total.
analisar.clientes_box(dados_treino_bruto, categoricas[2], numericas[-4])

Mesma interpreção que ao aos clientes de serviço internacional, sem variação consirável.

In []:
# Análise de estados X minutos total.
analisar.clientes_box(dados_treino_bruto, categoricas[3], numericas[-4], 51)

Aqui conseguimos notar que o estado Connecticut apresenta a maior dispersão nos valores, um desvio padrão de 111 minutos.

E os estados Arizona e Dakota do Norte apresentam a menor dispersão de minutos - ~77 minutos.

O estado de Indiana possui a maior média de minutos e apresenta uma taxa de ~14,5% inferior à média de churn dos estados , talvez valha a pena investir para aumentar os clientes no estado.

In []:
# Análise de região X minutos total.
analisar.clientes_box(dados_treino_bruto, categoricas[4], numericas[-4])

Já em relação às regiões não vemos uma diferença significativa a não ser pelo fato de que a região Sul apresenta a maior concentração de outliers e a região Centro Oeste apresenta a maior concentração de clientes com uma quantidade de outliers abaixo do limite inferior, ou seja, é a região onde os clientes possuem a menor minutagem.

In []:
# Análise de código de área X Ligações totais.
analisar.clientes_box(dados_treino_bruto, categoricas[0], numericas[-3])
In []:
# Histograma e QQPlot - Ligações total
analisar.probahist(dados_treino_bruto, 'ligacoes_total')

Não há diferença significativa entre a quantidade de ligações, porém percebemos alguns outliers.

Através do histograma podemos notar que a maior centração de valores para a quantidade de ligações está entre 300 e 329 ligações. Também vemos que apresenta uma forma muito similar de uma distribuição normal.

In []:
# Análise de serviço internacional X Ligações totais.
analisar.clientes_box(dados_treino_bruto, categoricas[1], numericas[-3])

Não vemos diferença significação entre a quantidade de ligações e os clientes que possuem ou não serviço internacional. Embora conseguimos ver que os outliers se concentram nos clientes que não possuem serviço internacional.

In []:
# Análise de serviço de caixa postal X Ligações totais.
analisar.clientes_box(dados_treino_bruto, categoricas[2], numericas[-3])

Não vemos diferença significativa entre ligações total e serivço de caixa postal.

In []:
# Análise de estados X Ligações totais.
analisar.clientes_box(dados_treino_bruto, categoricas[3], numericas[-3], 51)

Os estados Flórida e Georgia são os estados com a maior média de ligações, entretanto, a Flórida apresenta um desvião padrão ~18% maior.

In []:
# Análise de estados X Ligações totais.
analisar.clientes_box(dados_treino_bruto, categoricas[4], numericas[-3])

Não vemos uma diferença significativa no número de ligações entre as regiões.

In []:
# Análise de código de área X Taxa total.
analisar.clientes_box(dados_treino_bruto, categoricas[0], numericas[-2])
In []:
# Histograma e QQPlot - Taxa total
analisar.probahist(dados_treino_bruto, 'taxa_total')

Sem diferença significativa na quantidade total de taxas pagas entre os códigos de áreas.

No histograma conseguimos notar que os valores de taxa pagas estão concentrados entre 56 e 60 unidades monetárias e com o QQplot constatamos que se comportam muito próximo de uma distribuição normal.

In []:
# Análise de serviço internacional X Taxa total.
analisar.clientes_box(dados_treino_bruto, categoricas[1], numericas[-2])

Sem diferença significativa.

In []:
# Análise de serviço de caixa postal X Taxa total.
analisar.clientes_box(dados_treino_bruto, categoricas[2], numericas[-2])

Sem diferença significativa.

In []:
# Análise de código de área X Taxa total.
analisar.clientes_box(dados_treino_bruto, categoricas[3], numericas[-2], 51)

Sem diferença significativa.

In []:
# Análise de código de área X Taxa total.
analisar.clientes_box(dados_treino_bruto, categoricas[4], numericas[-2])

Sem diferença significativa.

Como vimos nos boxplots temos diversos outliers em nossos dados em todas as variáveis. Vamos criar uma variável com os outliers e passar à seguimentação de clientes.

In []:
# Criando variáveis com os valores do primeiro e terceiro quartil e calculando a amplitude.
Q1 = dados_treino_bruto.quantile(0.25)
Q3 = dados_treino_bruto.quantile(0.75)
interquantile = Q3-Q1
In []:
# Criando um dataset separado para remoção dos outliers.
outliers = dados_treino_bruto[interquantile.index]
outliers = outliers[(outliers < (Q1 - 1.5* interquantile)) | (outliers > (Q3 + 1.5 * interquantile))].fillna(0)
outliers_index = []
outliers_values = {}
for coluna in outliers.columns:
    outliers_values[coluna] = [valor for valor in outliers[coluna] if valor!=0 ]
    outliers_index.extend([index for index, valor in enumerate(outliers[coluna]) if valor!=0 ])
outliers_index = set(outliers_index)
treino_no_outliers = dados_treino_bruto.drop(outliers_index, axis=0)
print('Total de linha nos dados: ', len(treino_no_outliers))

# Salvando os dados num arquivo csv.
treino_no_outliers.to_csv('treino_no_outliers.csv')
Total de linha nos dados:  2501

Para sabermos se realmente poderemos prosseguir sem os outliers, vejamos como os valores de nossa variável independente ficaram após a remoção dos outliers.

In []:
# Verificando valor COM os outliers
print('Valores da variável "CHURN" antes da remoção dos Outliers:\n\n',
      dados_treino_bruto.churn.value_counts())

# Verificando valor SEM os outliers
print('\n\nValores da variável "CHURN" após remoção dos Outliers:\n\n', 
      treino_no_outliers['churn'].value_counts())
Valores da variável "CHURN" antes da remoção dos Outliers:

 0    2850
1     483
Name: churn, dtype: int64


Valores da variável "CHURN" após remoção dos Outliers:

 0    2501
Name: churn, dtype: int64

Conforme podemos ver acima todos os clientes com churn estão associados à algum Outlier. Sendo assim, não poderemos removê-los de nossos dados.

Passaremos à segmentação de clientes, para tanto, utilizaremos o algoritmo K-means para clusterização ou agrupamento dos clientes. Criaremos tês tipos de agrupamento: por total de minutos e ligações. Para definição da melhor quantidade de grupos utilizaremos o Elbow Method#:~:text=In%20cluster%20analysis%2C%20the%20elbow,number%20of%20clusters%20to%20use.)

In []:
# carregando o pacote Kmeans
from sklearn.cluster import KMeans

sse={}
for k in range(1, 10):
    kmeans = KMeans(n_clusters=k, max_iter=1000).fit(dados_treino_bruto[['minutos_total']])
    sse[k] = kmeans.inertia_
plt.figure(figsize=(8,6))
plt.plot(list(sse.keys()), list(sse.values()))
plt.xlabel("Number of cluster")
plt.show()

Ao propósito desse projeto seguiremos com um total de 3 clusters.

Bora por a mão na massa.

In []:
# Criando um loop para criação dos clusters.
for coluna in numericas[15:17]:
    kmeans = KMeans(n_clusters=3).fit(dados_treino_bruto[[coluna]])
    dados_treino_bruto[str('cluster_'+coluna)] = kmeans.predict(dados_treino_bruto[[coluna]])
In []:
# Verificandoo cluster de minutos
dados_treino_bruto.groupby('cluster_minutos_total')['minutos_total'].describe()
Out[]:
count mean std min 25% 50% 75% max
cluster_minutos_total
0 1549.0 590.385926 30.722777 534.6 563.4 592.1 615.4 645.6
1 917.0 700.948419 44.167404 645.7 665.6 690.7 725.1 885.0
2 867.0 479.132411 45.341871 284.3 455.6 490.0 514.2 534.5
In []:
# Verificandoo cluster de ligações
dados_treino_bruto.groupby('cluster_ligacoes_total')['ligacoes_total'].describe()
Out[]:
count mean std min 25% 50% 75% max
cluster_ligacoes_total
0 937.0 347.807898 16.961162 328.0 334.0 343.0 357.0 418.0
1 903.0 264.198228 17.219321 194.0 255.0 268.0 278.0 285.0
2 1493.0 306.606832 11.663806 286.0 297.0 307.0 316.0 327.0
In []:
# Reordenando o cluster.
dados_treino_bruto['cluster_minutos_total'] = dados_treino_bruto['cluster_minutos_total'].map({0:1, 1:2, 2:0})
dados_treino_bruto['cluster_ligacoes_total'] = dados_treino_bruto['cluster_ligacoes_total'].map({0:2, 1:0, 2:1})

# Salvando num arquivo csv.
dados_treino_bruto.to_csv('treino_clusterizado.csv')

Agora criaremos um score geral para cada cliente conforme sua classificação em cada cluster.

In []:
# Criando uma nova coluna com o score geral.
dados_treino_bruto['score_geral'] = dados_treino_bruto['cluster_minutos_total'] + dados_treino_bruto['cluster_ligacoes_total']
dados_treino_bruto.groupby('score_geral')[['minutos_total', 'ligacoes_total']] \
    .mean().sort_values(['ligacoes_total', 'minutos_total'])
Out[]:
minutos_total ligacoes_total
score_geral
0 476.908000 265.112000
1 536.049874 285.187657
2 591.903863 305.416309
3 643.115632 327.722989
4 703.764173 348.759843
In []:
# Criando um subset para plotagem da segmentação dos clientes.
clusters = dados_treino_bruto[['minutos_total', 'ligacoes_total', 'score_geral']]
clusters['score_geral'] = clusters['score_geral'].map({0:'E', 1:'D', 2:'C', 3:'B', 4:'A'})
clusters['score_geral'] = clusters['score_geral'].astype('category')
fig, ax = plt.subplots(figsize=(10,8))
sns.scatterplot(data=clusters, x='minutos_total', y='ligacoes_total', hue='score_geral')
plt.title('Divisão de clientes - Segmentação por Kmeans (3 clusters)', size=15)
Out[]:
Text(0.5, 1.0, 'Divisão de clientes - Segmentação por Kmeans (3 clusters)')
In []:
# Salvando as informações dos clusters num arquivo CSV.
clusters.to_csv('clusters.csv')

# Analisando informações estatísticas dos scores.
clusters.groupby('score_geral')[['ligacoes_total', 'minutos_total']].describe()
Out[]:
ligacoes_total minutos_total
count mean std min 25% 50% 75% max count mean std min 25% 50% 75% max
score_geral
A 254.0 348.759843 17.899848 328.0 334.00 344.0 359.0 410.0 254.0 703.764173 46.363563 645.8 666.800 694.2 732.225 851.8
B 870.0 327.722989 24.546760 286.0 307.25 328.0 344.0 400.0 870.0 643.115632 66.086272 534.7 590.525 642.0 688.375 882.2
C 1165.0 305.416309 30.829917 210.0 289.00 306.0 323.0 418.0 1165.0 591.903863 78.777479 284.3 544.600 592.8 636.300 885.0
D 794.0 285.187657 26.057533 194.0 268.00 285.0 307.0 327.0 794.0 536.049874 67.000905 319.9 490.075 536.3 592.600 645.3
E 250.0 265.112000 16.671268 211.0 256.00 269.0 278.0 285.0 250.0 476.908000 48.931139 301.5 449.450 489.2 515.100 534.3

Aqui podemos retornar ao Kmeans e aumentarmos a quantidade de cluster para maior divisão dos clientes ou podemos subdividir os clientes em subcategorias dentro de cada score.

Conforme vemos no gráfico Divisão de clientes acima, podemos ver que temos 5 categorias de clientes sendo que as categorias do meio (B, C e D) possuem sub grupos.

Vejamos como os clientes foram divididos:

Score A: Aqui temos os clientes que mais fazem ligações e são os que mais demoram em ligações. Entretanto é o segundo grupo com menos clientes.

Score B: aqui temos o grupo com clientes que fazem um número médio de ligações, porém ficam bastante tempo nessas ligações e temos os clientes que ficam um tempo médio nas ligações, porém fazem um número alto de ligações. É o segundo grupo com mais clientes.

Score C: aqui temos o grupo com mais clientes e também o que apresenta maior variação de perfis nos clientes. Temos três subcategorias:

  • Subgrupo C-1 --> Clientes que fazem poucas ligaçoes, mas ficam bastante tempo nessas ligaçoes.
  • Subgrupo C-2 --> Digamos que é onde fica a maior concentração dos clientes. São os clientes que fazem um número médio tanto em ligaçoes quanto no tempo (minutos) em ligações.
  • Subgrupo C-3 --> Clientes que fazem muitas ligaçoes, entretanto ficam pouco tempo nas ligaçoes.

Score D: aqui temos o quarto grupo de clientes. Nele estão os clientes que fazem poucas ligações e ficam um tempo mediano em cada ligação e os clientes que fazem num número mediano de ligações, mas ficam pouco tempo nessas ligações.

Score E: grupo com menor quantidade de clientes, entretanto é o grupo que contém os clientes que menos fazem ligações e menos tempo gastam nessas ligações.

Podem ser realizadas diferentes estratégias para cada grupo e subgrupo de clientes de acordo com o objetivo da empresa.

Passaremos à etapa de engenharia de atributos para então seguirmos para a criação de uma regressão logística. Para tal, precisamos realizar os seguintes passos:

  • Separação dos dados em treino e teste;
  • Transformação das variáveis categoricas em dummy;
  • Balanceamento da variável churn;
  • Remoção de variáveis com multicoloninearidade; e
  • Criação regressão logística.

Vamos criar algumas funções para nos ajudar com as 3 primeiras etapas acima.

In []:
def treino_teste(x_data, y_data, siz=0.3, state=0):
    # Importando o pacote necessário
    from sklearn.model_selection import train_test_split
    X_train, X_test, y_train, y_test = train_test_split(x_data, y_data, test_size=siz, random_state=state)
    return X_train, X_test, y_train, y_test

def dummyes(data):
    # criando uma cópia dos dados.
    dados = data.copy()
    
    # Importando o pacote necessário
    from sklearn.preprocessing import LabelEncoder
    le = LabelEncoder() # Criando uma instância dele
    
    dummy_columns = [] # Criando uma lista para salvarmos as variáveis com 
    
    for column in dados.columns:
        if dados[column].dtype == object:
            if dados[column].nunique() == 2:
                # aplicando o  Label Encoder às variáveis categóricas binárias
                dados[column] = le.fit_transform(dados[column]) 
            else:
                dummy_columns.append(column)
                
    # Aplicando a função get dummies às variáveis categóricas não binárias.
    dados = pd.get_dummies(data = dados, columns = dummy_columns)
    
    return dados

def balance(x_data, y_data, state=0):
    # Importando o pacote necessário
    from imblearn.over_sampling import SMOTE
    
    # Criando uma variável com as colunas dos dados.
    colunas = x_data.columns
    
    # criando uma instância do SMOTE
    os = SMOTE(random_state=state) 

    x_smoted, y_smoted = os.fit_resample(x_data, y_data)
    
    x_df = pd.DataFrame(x_smoted, columns=colunas)
    y_df = pd.DataFrame(y_smoted, columns=['churn'])
    
    xy_df = pd.concat([x_df, y_df], axis=1)
    
    return xy_df
In []:
# criando variáveis separadas às variáveis dependentes e independente.
x = dados_treino_bruto.loc[:, dados_treino_bruto.columns != 'churn']
y = dados_treino_bruto[['churn']]

# Seperando os dados em treino e teste (70% - 30%)
x_treino, x_teste, y_treino, y_teste = treino_teste(x, y)
In []:
# Criando variáveis dummies
x_treino_dummie = dummyes(x_treino)
x_teste_dummie = dummyes(x_teste)

# aplicando o balanceamento aos dados de treino e teste.
xy_treino_smoted = balance(x_treino_dummie, y_treino)
xy_teste_smoted = balance(x_teste_dummie, y_teste)

Agora podemos passar à última etapa de nossos dados que que trata da selação das melhores variáveis para criação do modelo de regressão logística.

Iniciaremos removendo as variáveis com Multicolinearidade, para isso consideraremos uma alta relação quando as variáveis tiverem uma correlação superior à 0.7

Simbora!

In []:
# Criando uma variável do tipo set para armazenagem das variáveis altamente relacionáveis
treino_corr = xy_treino_smoted.corr()
colunas_rela = set()
for index_col, col in enumerate(treino_corr.columns):
    for index_ind, ind in enumerate(treino_corr.index):
        if (abs(treino_corr.loc[ind, col]) > 0.7 and index_col>index_ind):
            colunas_rela.add(col)

# Verificando a quantidade de variáveis.
print('Número de variáveis altamente relacionáveis: ', len(colunas_rela))

# Imprimindo as variáveis.
print(f'\nVariáveis: {colunas_rela}')
Número de variáveis altamente relacionáveis:  9

Variáveis: {'taxa_lig_tarde', 'taxa_total', 'Qntd_servicos', 'taxa_lig_internacional', 'minutos_total', 'mensagens_voz', 'cluster_minutos_total', 'taxa_lig_manha', 'taxa_lig_noite'}

Faremos a remoção das variáveis altamente relacionáveis e seguiremos com a criação da regressão logística para análise.

In []:
# Removendo as variáveis
xy_treino_smoted.drop(colunas_rela, axis=1, inplace=True)

# Renomeando as variáveis e criando fórmula para criação da regressão logística.
xy_treino_smoted.columns = [x.replace(' ','_') for x in xy_treino_smoted.columns]
variaveis = 'tempo_conta' 
for coluna in xy_treino_smoted.columns:
    if coluna not in['churn', 'tempo_conta']:
        variaveis = variaveis + ' + ' + coluna
In []:
# Importando os pacotes necessários
import statsmodels.formula.api as smf
import statsmodels.api as sm

# Criado o modelo e imprimindo
ols = smf.ols(formula=f'churn ~ {variaveis}', data=xy_treino_smoted).fit()
print(ols.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                  churn   R-squared:                       0.734
Model:                            OLS   Adj. R-squared:                  0.729
Method:                 Least Squares   F-statistic:                     147.4
Date:                Fri, 13 Aug 2021   Prob (F-statistic):               0.00
Time:                        15:50:28   Log-Likelihood:                -250.77
No. Observations:                3968   AIC:                             649.5
Df Residuals:                    3894   BIC:                             1115.
Df Model:                          73                                         
Covariance Type:            nonrobust                                         
===============================================================================================
                                  coef    std err          t      P>|t|      [0.025      0.975]
-----------------------------------------------------------------------------------------------
Intercept                       0.2404      0.071      3.371      0.001       0.101       0.380
tempo_conta                  3.096e-05      0.000      0.277      0.782      -0.000       0.000
srv_internacional               0.2225      0.015     15.330      0.000       0.194       0.251
srv_caixa_postal               -0.0731      0.011     -6.552      0.000      -0.095      -0.051
minutos_lig_manha               0.0011      0.000      9.174      0.000       0.001       0.001
ligacoes_manha                 -0.0491      0.008     -6.047      0.000      -0.065      -0.033
minutos_lig_tarde               0.0007      0.000      5.721      0.000       0.000       0.001
ligacoes_tarde                 -0.0493      0.008     -6.069      0.000      -0.065      -0.033
minutos_lig_noite               0.0005      0.000      4.240      0.000       0.000       0.001
ligacoes_noite                 -0.0497      0.008     -6.105      0.000      -0.066      -0.034
minutos_lig_internacional       0.0078      0.002      4.792      0.000       0.005       0.011
ligacoes_internacional         -0.0554      0.008     -6.648      0.000      -0.072      -0.039
ligacoes_SAC                   -0.0049      0.009     -0.573      0.567      -0.022       0.012
ligacoes_total                  0.0495      0.008      6.096      0.000       0.034       0.065
cluster_ligacoes_total          0.0337      0.014      2.344      0.019       0.006       0.062
score_geral                    -0.0267      0.013     -2.008      0.045      -0.053      -0.001
codigo_area_area_code_408      -0.0281      0.016     -1.732      0.083      -0.060       0.004
codigo_area_area_code_415      -0.0272      0.014     -1.995      0.046      -0.054      -0.000
codigo_area_area_code_510      -0.0372      0.016     -2.288      0.022      -0.069      -0.005
estado_Alabama                 -0.6961      0.042    -16.409      0.000      -0.779      -0.613
estado_Alaska                  -0.7958      0.056    -14.304      0.000      -0.905      -0.687
estado_Arizona                 -0.7665      0.052    -14.688      0.000      -0.869      -0.664
estado_Arkansas                -0.5661      0.046    -12.176      0.000      -0.657      -0.475
estado_California              -0.5265      0.061     -8.654      0.000      -0.646      -0.407
estado_Colorado                -0.6721      0.051    -13.075      0.000      -0.773      -0.571
estado_Connecticut             -0.6799      0.055    -12.315      0.000      -0.788      -0.572
estado_Delaware                -0.6978      0.052    -13.524      0.000      -0.799      -0.597
estado_District_of_Columbia    -0.7062      0.050    -14.238      0.000      -0.803      -0.609
estado_Florida                 -0.6644      0.052    -12.864      0.000      -0.766      -0.563
estado_Georgia                 -0.6863      0.050    -13.822      0.000      -0.784      -0.589
estado_Hawaii                  -0.7655      0.057    -13.496      0.000      -0.877      -0.654
estado_Idaho                   -0.7167      0.048    -14.829      0.000      -0.811      -0.622
estado_Illinois                -0.7156      0.057    -12.556      0.000      -0.827      -0.604
estado_Indiana                 -0.6999      0.056    -12.559      0.000      -0.809      -0.591
estado_Iowa                    -0.7638      0.063    -12.091      0.000      -0.888      -0.640
estado_Kansas                  -0.6216      0.055    -11.398      0.000      -0.729      -0.515
estado_Kentucky                -0.5733      0.045    -12.679      0.000      -0.662      -0.485
estado_Louisiana               -0.7398      0.051    -14.496      0.000      -0.840      -0.640
estado_Maine                   -0.5645      0.059     -9.623      0.000      -0.680      -0.450
estado_Maryland                -0.6080      0.046    -13.250      0.000      -0.698      -0.518
estado_Massachusetts           -0.6233      0.056    -11.168      0.000      -0.733      -0.514
estado_Michigan                -0.6408      0.054    -11.894      0.000      -0.746      -0.535
estado_Minnesota               -0.6547      0.053    -12.273      0.000      -0.759      -0.550
estado_Mississippi             -0.5849      0.045    -12.995      0.000      -0.673      -0.497
estado_Missouri                -0.7064      0.055    -12.781      0.000      -0.815      -0.598
estado_Montana                 -0.6080      0.051    -11.981      0.000      -0.708      -0.509
estado_Nebraska                -0.7072      0.057    -12.373      0.000      -0.819      -0.595
estado_Nevada                  -0.5833      0.049    -11.970      0.000      -0.679      -0.488
estado_New_Hampshire           -0.6366      0.058    -10.934      0.000      -0.751      -0.522
estado_New_Jersey              -0.5841      0.056    -10.383      0.000      -0.694      -0.474
estado_New_Mexico              -0.7532      0.051    -14.664      0.000      -0.854      -0.652
estado_New_York                -0.5601      0.053    -10.560      0.000      -0.664      -0.456
estado_North_Carolina          -0.6133      0.045    -13.610      0.000      -0.702      -0.525
estado_North_Dakota            -0.7092      0.055    -12.864      0.000      -0.817      -0.601
estado_Ohio                    -0.7230      0.053    -13.748      0.000      -0.826      -0.620
estado_Oklahoma                -0.6800      0.048    -14.304      0.000      -0.773      -0.587
estado_Oregon                  -0.6808      0.048    -14.249      0.000      -0.774      -0.587
estado_Pennsylvania            -0.6229      0.062    -10.028      0.000      -0.745      -0.501
estado_Rhode_Island            -0.7039      0.057    -12.385      0.000      -0.815      -0.592
estado_South_Carolina          -0.5457      0.047    -11.557      0.000      -0.638      -0.453
estado_South_Dakota            -0.6308      0.058    -10.863      0.000      -0.745      -0.517
estado_Tennessee               -0.7118      0.048    -14.953      0.000      -0.805      -0.618
estado_Texas                   -0.4777      0.044    -10.765      0.000      -0.565      -0.391
estado_Utah                    -0.6746      0.049    -13.904      0.000      -0.770      -0.579
estado_Vermont                 -0.6896      0.054    -12.801      0.000      -0.795      -0.584
estado_Virginia                -0.7574      0.044    -17.396      0.000      -0.843      -0.672
estado_Washington              -0.5805      0.051    -11.365      0.000      -0.681      -0.480
estado_West_Virginia           -0.6680      0.042    -15.882      0.000      -0.750      -0.586
estado_Wisconsin               -0.7430      0.054    -13.857      0.000      -0.848      -0.638
estado_Wyoming                 -0.7441      0.049    -15.332      0.000      -0.839      -0.649
regiao_Midwest                  0.0066      0.036      0.184      0.854      -0.064       0.077
regiao_Northeast               -0.0083      0.040     -0.210      0.834      -0.086       0.069
regiao_South                   -0.0135      0.022     -0.620      0.535      -0.056       0.029
regiao_West                     0.0191      0.030      0.646      0.519      -0.039       0.077
==============================================================================
Omnibus:                     1312.608   Durbin-Watson:                   1.997
Prob(Omnibus):                  0.000   Jarque-Bera (JB):             3743.915
Skew:                           1.754   Prob(JB):                         0.00
Kurtosis:                       6.215   Cond. No.                     2.09e+04
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 2.09e+04. This might indicate that there are
strong multicollinearity or other numerical problems.

Podemos ver que nem todas as variáveis são interessantes ao nosso modelo. Para termos uma significância de 0.05 utilizaremos apenas as variáveis om p-values inferiores à 0.05.

In []:
# criando nova variável com as colunas
variaveis = 'srv_internacional' 
for coluna in xy_treino_smoted.columns:
    if coluna in ols.pvalues[ols.pvalues<0.05].index and coluna!='srv_internacional':
        variaveis = variaveis + ' + ' + coluna

# Criando versão 2 do modelo e imprimindo
ols_v2 = smf.ols(formula=f'churn ~ {variaveis}', data=xy_treino_smoted).fit()
print(ols_v2.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                  churn   R-squared:                       0.734
Model:                            OLS   Adj. R-squared:                  0.729
Method:                 Least Squares   F-statistic:                     163.1
Date:                Fri, 13 Aug 2021   Prob (F-statistic):               0.00
Time:                        15:52:17   Log-Likelihood:                -253.00
No. Observations:                3968   AIC:                             640.0
Df Residuals:                    3901   BIC:                             1061.
Df Model:                          66                                         
Covariance Type:            nonrobust                                         
===============================================================================================
                                  coef    std err          t      P>|t|      [0.025      0.975]
-----------------------------------------------------------------------------------------------
Intercept                       0.2453      0.069      3.536      0.000       0.109       0.381
srv_internacional               0.2241      0.014     15.512      0.000       0.196       0.252
srv_caixa_postal               -0.0733      0.011     -6.576      0.000      -0.095      -0.051
minutos_lig_manha               0.0011      0.000      9.170      0.000       0.001       0.001
ligacoes_manha                 -0.0451      0.003    -15.691      0.000      -0.051      -0.039
minutos_lig_tarde               0.0007      0.000      5.657      0.000       0.000       0.001
ligacoes_tarde                 -0.0453      0.003    -15.659      0.000      -0.051      -0.040
minutos_lig_noite               0.0005      0.000      4.191      0.000       0.000       0.001
ligacoes_noite                 -0.0456      0.003    -15.826      0.000      -0.051      -0.040
minutos_lig_internacional       0.0078      0.002      4.765      0.000       0.005       0.011
ligacoes_internacional         -0.0512      0.003    -15.328      0.000      -0.058      -0.045
ligacoes_total                  0.0455      0.003     15.833      0.000       0.040       0.051
cluster_ligacoes_total          0.0316      0.014      2.206      0.027       0.004       0.060
score_geral                    -0.0253      0.013     -1.906      0.057      -0.051       0.001
codigo_area_area_code_415      -0.0113      0.010     -1.137      0.256      -0.031       0.008
codigo_area_area_code_510      -0.0203      0.013     -1.586      0.113      -0.045       0.005
estado_Alabama                 -0.7290      0.034    -21.133      0.000      -0.797      -0.661
estado_Alaska                  -0.7955      0.046    -17.271      0.000      -0.886      -0.705
estado_Arizona                 -0.7664      0.042    -18.324      0.000      -0.848      -0.684
estado_Arkansas                -0.5959      0.040    -14.890      0.000      -0.674      -0.517
estado_California              -0.5254      0.052    -10.053      0.000      -0.628      -0.423
estado_Colorado                -0.6740      0.041    -16.610      0.000      -0.754      -0.594
estado_Connecticut             -0.7077      0.037    -19.218      0.000      -0.780      -0.635
estado_Delaware                -0.7305      0.045    -16.141      0.000      -0.819      -0.642
estado_District_of_Columbia    -0.7372      0.043    -17.013      0.000      -0.822      -0.652
estado_Florida                 -0.6961      0.046    -15.286      0.000      -0.785      -0.607
estado_Georgia                 -0.7189      0.043    -16.714      0.000      -0.803      -0.635
estado_Hawaii                  -0.7648      0.047    -16.143      0.000      -0.858      -0.672
estado_Idaho                   -0.7155      0.037    -19.380      0.000      -0.788      -0.643
estado_Illinois                -0.7281      0.042    -17.182      0.000      -0.811      -0.645
estado_Indiana                 -0.7122      0.041    -17.534      0.000      -0.792      -0.633
estado_Iowa                    -0.7745      0.051    -15.303      0.000      -0.874      -0.675
estado_Kansas                  -0.6327      0.039    -16.080      0.000      -0.710      -0.556
estado_Kentucky                -0.6020      0.039    -15.448      0.000      -0.678      -0.526
estado_Louisiana               -0.7727      0.045    -17.305      0.000      -0.860      -0.685
estado_Maine                   -0.5904      0.042    -14.018      0.000      -0.673      -0.508
estado_Maryland                -0.6402      0.039    -16.526      0.000      -0.716      -0.564
estado_Massachusetts           -0.6497      0.038    -17.033      0.000      -0.724      -0.575
estado_Michigan                -0.6518      0.038    -17.108      0.000      -0.726      -0.577
estado_Minnesota               -0.6668      0.037    -17.843      0.000      -0.740      -0.594
estado_Mississippi             -0.6173      0.038    -16.379      0.000      -0.691      -0.543
estado_Missouri                -0.7177      0.040    -17.892      0.000      -0.796      -0.639
estado_Montana                 -0.6086      0.040    -15.263      0.000      -0.687      -0.530
estado_Nebraska                -0.7193      0.043    -16.853      0.000      -0.803      -0.636
estado_Nevada                  -0.5809      0.038    -15.362      0.000      -0.655      -0.507
estado_New_Hampshire           -0.6647      0.041    -16.145      0.000      -0.745      -0.584
estado_New_Jersey              -0.6102      0.039    -15.821      0.000      -0.686      -0.535
estado_New_Mexico              -0.7532      0.041    -18.521      0.000      -0.833      -0.673
estado_New_York                -0.5865      0.034    -17.379      0.000      -0.653      -0.520
estado_North_Carolina          -0.6455      0.038    -16.994      0.000      -0.720      -0.571
estado_North_Dakota            -0.7210      0.040    -18.031      0.000      -0.799      -0.643
estado_Ohio                    -0.7356      0.036    -20.296      0.000      -0.807      -0.665
estado_Oklahoma                -0.7125      0.041    -17.526      0.000      -0.792      -0.633
estado_Oregon                  -0.6795      0.036    -18.717      0.000      -0.751      -0.608
estado_Pennsylvania            -0.6507      0.047    -13.948      0.000      -0.742      -0.559
estado_Rhode_Island            -0.7303      0.039    -18.577      0.000      -0.807      -0.653
estado_South_Carolina          -0.5765      0.040    -14.237      0.000      -0.656      -0.497
estado_South_Dakota            -0.6432      0.044    -14.697      0.000      -0.729      -0.557
estado_Tennessee               -0.7438      0.041    -18.186      0.000      -0.824      -0.664
estado_Texas                   -0.5096      0.037    -13.660      0.000      -0.583      -0.436
estado_Utah                    -0.6734      0.037    -18.065      0.000      -0.746      -0.600
estado_Vermont                 -0.7160      0.035    -20.408      0.000      -0.785      -0.647
estado_Virginia                -0.7909      0.036    -22.113      0.000      -0.861      -0.721
estado_Washington              -0.5806      0.040    -14.342      0.000      -0.660      -0.501
estado_West_Virginia           -0.6998      0.034    -20.424      0.000      -0.767      -0.633
estado_Wisconsin               -0.7559      0.038    -20.055      0.000      -0.830      -0.682
estado_Wyoming                 -0.7438      0.037    -20.069      0.000      -0.816      -0.671
==============================================================================
Omnibus:                     1308.603   Durbin-Watson:                   1.994
Prob(Omnibus):                  0.000   Jarque-Bera (JB):             3723.629
Skew:                           1.750   Prob(JB):                         0.00
Kurtosis:                       6.206   Cond. No.                     1.09e+04
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 1.09e+04. This might indicate that there are
strong multicollinearity or other numerical problems.

Podemos ver que nosso R-squared é de ~73%, ou seja, nossas variáveis independentes estão explicando 73% da média de churn. Podemos aumentar o número de nossas variáveis para vermos se conseguimos melhorar nosso R_squared, porém ao projeto prosseguiremos com o que temos.

Ao projeto seguiremos com as variáveis que temos. Vejamos algumas informações da tabela acima:

  • Possuir serviço internacional aumenta a média de churn em 0.2241.
  • É possível notar que praticamente temos um coeficiente negativo em todas as variáveis, porém, ligacoes_total e cluster_ligacoes_total possuem um coeficiente positivo e são as variáveis que mais pesam quando falamos em alteração na média de churn. A cada unidade aumentada nessas variáveis aumenta-se a média de churn em: 0.0455 e 0.0316 respecitvamente. Portanto quanto mais ligações o cliente fizer mais chances ele tem de dar churn. Se usarmos a média de ligações multiplicada pela taxa (300 x 0.0455) aumenta-se as chances de churn em 13,65%.

Com a remoção das variáveis conseguimos notar que o p-value de algumas variáveis alteraram. Entretanto seguiremos com todas as variáveis.

In []:
def treina_avalia(data, y, algoritmo, folds=10, seed=2):
    # Importando variáveis externas
    global ols_v2
    
    # Pacotes para transformação dos dados
    from sklearn.preprocessing import MinMaxScaler
    from sklearn.preprocessing import StandardScaler    
    
    # Pacotes para avaliação dos modelos criados.
    from sklearn.model_selection import KFold
    from sklearn.model_selection import cross_val_score
    
    # Separando a variável independente das demais.
    x = data.loc[:, ols_v2.params[1:].index].values
    y = data[y]

    x = MinMaxScaler(feature_range = (0, 1)).fit_transform(x)
    x = StandardScaler().fit_transform(x)

    # Criando e treinando o modelo.
    modelo = algoritmo.fit(x, y)

    # Criando o separador dos dados em folds.
    separador = KFold(folds, shuffle=True, random_state=seed)

    # Criando o modelo.
    Validation = cross_val_score(modelo, x, y, cv=separador, scoring='accuracy')

    # criando um dataframe para computar as informações.
    resultados = pd.DataFrame({'Acuracia': Validation.mean(),
                              'Desvio': Validation.std()}, index=[str(algoritmo)[0:-2]])
    
    print(resultados)

    return modelo

def validation_predict(data, y, algoritmo):
    
    # importando os pacotes necessiários
    from sklearn.preprocessing import MinMaxScaler
    from sklearn.preprocessing import StandardScaler
    from sklearn.metrics import confusion_matrix
    from sklearn.metrics import accuracy_score
    from sklearn.metrics import roc_curve
    from sklearn.metrics import auc
    from sklearn.metrics import roc_auc_score
    from sklearn.metrics import plot_roc_curve
    
    # Separando a variável independente das demais.
    x_teste = data.loc[:, ols_v2.params[1:].index]
    y_teste = data[y]
    
    # aplicando transformações aos dados.
    x_teste = MinMaxScaler(feature_range = (0, 1)).fit_transform(x_teste)
    x_teste = StandardScaler().fit_transform(x_teste)
    
    # Realizando as predições.
    predicts = algoritmo.predict(x_teste)

    # Verificando a AUC do modelo
    fpr, tpr, thresholds = roc_curve(y_teste, predicts, pos_label=1)

    # Verificando estatítiscas do modelo.
    print('\n\nAcurácia aos dados de teste:\n\n')
    print('Accuracy: ', round(accuracy_score(y_teste, predicts), ndigits=3)*100)
    print('\nConfusion Matrix:\n', confusion_matrix(y_teste, predicts))
    print('\nAUC (Area Under Curve): ', round(auc(fpr, tpr), ndigits=3)*100)
    
    print('\nROC CURVE:')
    plot_roc_curve(algoritmo, x_teste, y_teste)  
    plt.show()  
In []:
# Limperaremos os dados de teste conforme fizemos com os de treino para seguirmos os próximos passos.

# Removendo as variáveis
xy_teste_smoted.drop(colunas_rela, axis=1, inplace=True)

# Renomeando as variáveis e criando fórmula para criação da regressão logística.
xy_teste_smoted.columns = [x.replace(' ','_') for x in xy_teste_smoted.columns]

Aqui utilizaremos dois algoritmos para criação e validação. Um deles será a própria regressão logística e o segundo será o o HistGradientBoostingClassifier.

Simbora ver o que conseguimos.

In []:
# Importando o pacote para regressão logística.
from sklearn.linear_model import LogisticRegression

# criando, treinando e avaliando o algoritmo.
logreg = treina_avalia(xy_treino_smoted, 'churn', LogisticRegression())

# Avaliando com os dados de teste.
validation_predict(xy_teste_smoted, 'churn', logreg)
                    Acuracia    Desvio
LogisticRegression  0.918594  0.019029


Acurácia aos dados de teste:


Accuracy:  92.0

Confusion Matrix:
 [[819  47]
 [ 92 774]]

AUC (Area Under Curve):  92.0

ROC CURVE:
In []:
# Importando o pacote para regressão logística.
from sklearn.experimental import enable_hist_gradient_boosting
from sklearn.ensemble import HistGradientBoostingClassifier

# criando, treinando e avaliando o algoritmo.
histgradboost = treina_avalia(xy_treino_smoted, 'churn', HistGradientBoostingClassifier())

# Avaliando com os dados de teste.
validation_predict(xy_teste_smoted, 'churn', histgradboost)
                                Acuracia    Desvio
HistGradientBoostingClassifier   0.94758  0.015336


Acurácia aos dados de teste:


Accuracy:  92.5

Confusion Matrix:
 [[826  40]
 [ 90 776]]

AUC (Area Under Curve):  92.5

ROC CURVE:

Podemos ver que ambos os algoritmos tiveram um desempenho similar quando os aplicamos aos dados de teste. Seguiremos com a regressão logística para sabermos a probabilidade de um determinado cliente dar churn utilizando um novo conjunto de dados.

Precisaremos realizar as mesmas transformações realizadas aos dados anteriormente.

In []:
# Removendo a coluna Unnamed
dados_teste_bruto.drop('Unnamed: 0', axis=1, inplace=True)
In []:
# Agregando o nome e região dos estados
dados_teste_bruto = dados_teste_bruto.merge(states_name, sort=True)

# removendo a coluna state antiga
dados_teste_bruto = dados_teste_bruto.drop('state', axis=1)
In []:
# Renomeando as colunas.
dados_teste_bruto.columns = ['tempo_conta', 'codigo_area', 'srv_internacional', 'srv_caixa_postal',  'mensagens_voz',
                             'minutos_lig_manha', 'ligacoes_manha', 'taxa_lig_manha', 'minutos_lig_tarde', 'ligacoes_tarde',
                              'taxa_lig_tarde', 'minutos_lig_noite', 'ligacoes_noite', 'taxa_lig_noite', 'minutos_lig_internacional',
                              'ligacoes_internacional', 'taxa_lig_internacional', 'ligacoes_SAC', 'churn', 'estado', 'regiao']

# Criando uma coluna com os minutos totais
dados_teste_bruto['minutos_total'] = dados_teste_bruto[[col for col in dados_teste_bruto.columns \
                                                           if 'minutos' in col]].sum(axis=1)

# criando uma coluna com o total de ligações
dados_teste_bruto['ligacoes_total'] = dados_teste_bruto[[col for col in dados_teste_bruto.columns \
                                                           if 'ligacoes' in col]].sum(axis=1)

# criando uma coluna com o total de taxa paga
dados_teste_bruto['taxa_total'] = dados_teste_bruto[[col for col in dados_teste_bruto.columns \
                                                           if 'taxa' in col]].sum(axis=1)

conditions = [
    (dados_teste_bruto['srv_internacional'] == 'yes') & (dados_teste_bruto['srv_caixa_postal'] == 'yes'), 
    (dados_teste_bruto['srv_internacional'] == 'yes') | (dados_teste_bruto['srv_caixa_postal'] == 'yes'),
    (dados_teste_bruto['srv_internacional'] == 'no') & (dados_teste_bruto['srv_caixa_postal'] == 'no')]

choices = [int(2), int(1), int(0)]

dados_teste_bruto['Qntd_servicos'] = np.select(conditions, choices, default=np.nan)

dados_teste_bruto['churn'] = [0 if x=='no' else 1 for x in dados_teste_bruto['churn']]
In []:
for coluna in numericas[15:17]:
    kmeans = KMeans(n_clusters=3).fit(dados_teste_bruto[[coluna]])
    dados_teste_bruto[str('cluster_'+coluna)] = kmeans.predict(dados_teste_bruto[[coluna]])
    
# Verificandoo cluster de minutos
dados_teste_bruto.groupby('cluster_minutos_total')['minutos_total'].describe()
Out[]:
count mean std min 25% 50% 75% max
cluster_minutos_total
0 443.0 700.260271 45.468532 646.3 665.55 688.7 720.8 858.2
1 485.0 489.365773 41.913377 303.2 470.20 498.4 521.3 540.5
2 739.0 592.220974 29.334801 540.7 566.40 592.3 616.7 645.4
In []:
# Verificandoo cluster de ligações
dados_teste_bruto.groupby('cluster_ligacoes_total')['ligacoes_total'].describe()
Out[]:
count mean std min 25% 50% 75% max
cluster_ligacoes_total
0 404.0 261.136139 15.937807 192.0 252.0 265.0 274.0 281.0
1 518.0 343.944015 17.403138 323.0 330.0 340.0 353.0 413.0
2 745.0 301.789262 11.932151 282.0 291.0 301.0 313.0 322.0
In []:
# Reordenando o cluster.
dados_teste_bruto['cluster_minutos_total'] = dados_teste_bruto['cluster_minutos_total'].map({0:2, 1:0, 2:1})
dados_teste_bruto['cluster_ligacoes_total'] = dados_teste_bruto['cluster_ligacoes_total'].map({0:0, 1:2, 2:1})

# Criando uma nova coluna com o score geral.
dados_teste_bruto['score_geral'] = dados_teste_bruto['cluster_minutos_total'] + dados_teste_bruto['cluster_ligacoes_total']
dados_teste_bruto.groupby('score_geral')[['minutos_total', 'ligacoes_total']] \
    .mean().sort_values(['ligacoes_total', 'minutos_total'])
Out[]:
minutos_total ligacoes_total
score_geral
0 490.570536 261.553571
1 533.670784 282.973872
2 586.845965 305.996491
3 642.575912 323.401460
4 699.273203 344.660131
In []:
# criando variáveis separadas às variáveis dependentes e independente.
y = dados_teste_bruto[['churn']]

# Aplicando criando variáveis dummies às variáveis categóricas
teste_dummie = dummyes(dados_teste_bruto)

# aplicando o balanceamento aos dados de treino e teste.
final_df = balance(teste_dummie, y)

# Removendo as variáveis
final_df.drop(colunas_rela, axis=1, inplace=True)

# Renomeando as variáveis e criando fórmula para criação da regressão logística.
final_df.columns = [x.replace(' ','_') for x in final_df.columns]
In []:
# importando pacotes para transformação
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import StandardScaler

x_final = final_df.loc[:, ols_v2.params[1:].index].values

# Aplicando as transformações aos dados
x_final = MinMaxScaler(feature_range = (0, 1)).fit_transform(x_final)
x_final = StandardScaler().fit_transform(x_final)
In []:
# criando um novo dataframe contendo os IDs dos clientes.
churn_prob = pd.DataFrame({'ID_cliente': range(1, len(x_final)+1)})
In []:
# Verificando a probabilidade de cada cliente dar churn
churn_prob['Probabilidade_churn'] = logreg.predict_proba(x_final)[:,1]*100
In []:
# verificando a probabilidade dos clientes
churn_prob = churn_prob.set_index('ID_cliente')
churn_prob.head()
Out[]:
Probabilidade_churn
ID_cliente
1 0.637575
2 0.622686
3 0.715309
4 0.276776
5 0.078932
In []:
# salvando os algoritmos e o resultado final em disco.
import pickle
pickle.dump(logreg, open('logisticregression.sav', 'wb'))
churn_prob.to_csv('probs_churn.csv', index=False)